Object Pascal
Overview Object Pascal is a draft for an object oriented ANSI/ISO standard of the venerable Pascal programming language (circa 1989-1990). The draft never made it to a full standard, but some Apple dialects are pretty close to it. Object Pascal is the programming language you use in Delphi. It is mostly similar to Turbo Pascal, but Borland has added some features to it. I will deal with these later. Object Pascal is obviously an object oriented language. For those who don't know what this is I'll give a brief summary in the next section. Those who are familiar with OOP can skip it. The Object Pascal dialect used in Delphi was basically a strongly (and incompatibly) enhanced version of the proposals in this draft is the native language of Delphi. (e..g in the original Object Pascal, all methods are virtual automatically). Delphi when started was regarded more as an IDE just like Visual Studio for RAD development purpose uptil Delphi version 6.0. However, from version 7.0 Delphi 7.0 itself was regarded as the language and the Object Pascal lost its name. Delphi has undergone through lot of hands first from Borland Inc to Inprise (which infact was Borland itself) and then to CodeGear and now eventually is being sold out to Embarcadero Technologies. The most recent release of Delphi as of now is Delphi XE launched in the year 2010. Name Issues Technically speaking the developers of Delphi no longer call the language "Object Pascal" but consider it a variant of Object Pascal called "The Delphi Programming Language". This may be due to many non-ANSI standard changes they desired to include or possibly to make their extensions proprietary. Another reason might be more trademark related. But if it looks like pascal and quacks like pascal... Free Pascal still calls the language Object Pascal, even though it is nearly 100% compatible to Delphi's version. Free Pascal is slowly also building a dialect closer to classical Object Pascal (for Mac Pascal compability). Free Pascal also features another object oriented Pascal for Mac GUI (COCOA) interfacing, see Objective Pascal Coding in Pascal Comments Delphi allows developers with three types of commentings. Both single line and multiple line commenting is allowed to the developers. The different types of comments allowed are - *'{this is a comment}' *'(* this is another comment *)' *'// this is a comment up to the end of the line' The opening curly bracket '{' initiates a comment while the closing curly bracket '}' suggests the termination of the multi line comment. Similar is the case with the combination of open bracket and star '(*' and star and closing bracket '*)' for multi line comments. A single line comment can be made by simple two forward slashes '//'. It should be noted that anything written after the '//' will be ignored by the compiler. The developer must note that the multi line comments '{' and '(*' are considered multi line comments only if they are not immediately followed by a dollar '$' sign, else they will be considered as compiler directives such as -'' ''{$R *.DFM} which a developer can commonly find in unit file associated with a form file. Syntax Highlighting To make it easier to read and write Pascal code, the Delphi editor has a feature called color syntax highlighting. Depending on the meaning in Pascal of the words you type in the editor, they are displayed using different colors. There are several 3rd party IDE plugins that offer enhancements for Syntax Highlighting. * cnWizards (open source) * Brilliant Code for Delphi. (commercial) * Castalia (No longer available, but has been integrated in later Delphi versions Expressions and Operators Operators and Precedence Unary Operators (Highest Precedence) ---- * '@' Address of the variable or function (returns a pointer) * 'not' Boolean or bitwise not Multiplicative and Bitwise Operators ---- * '*' Arithmetic multiplication or set intersection * '/' Floating-point division * 'div' Integer division * 'mod' Modulus (the remainder of integer division) * 'as' Allows a type-checked type conversion among at runtime (part of the RTTI support) * 'and' Boolean or bitwise and * 'shl' Bitwise left shift * 'shr' Bitwise right shift Additive Operators ---- * '+' Arithmetic addition, set union, string concatenation, pointer offset addition * '-' Arithmetic subtraction, set difference, pointer offset subtraction * 'or' Boolean or bitwise or * 'xor' Boolean or bitwise exclusive or Relational and Comparison Operators (Lowest Precedence) ---- * '=' Test whether equal * '<>' Test whether not equal * '<' Test whether less than * '>' Test whether greater than * '<=' Test whether less than or equal to, or a subset of a set * '>=' Test whether greater than or equal to, or a superset of a set * 'in' Test whether the item is a member of the set * 'is' Test whether object is type-compatible (another RTTI operator) Set Operators The set operators includes: union (+), difference (-), intersection (*), membership test (in), plus some relational operators. To add an element to a set, you can make the union of the set with another one that has only the element you need. Here's a Delphi example related to font styles: * Style := Style + fsBold; * Style := Style + fsItalic - fsUnderline; Variables Example: var Value: Integer; IsCorrect: Boolean; A, B: Char; begin Value := 10; IsCorrect := True; end; var Value: Integer = 10; IsCorrect: Boolean = True; Note: This initialization technique works only for global variables, not for variables declared inside the scope of a procedure or method. Constants When you declare a constant, the compiler can choose whether to assign a memory location to the constant, and save its value there, or to duplicate the actual value each time the constant is used. This second approach makes sense particularly for simple constants. Example: const Thousand = 1000; Pi = 3.14; const Thousand: Integer = 1000; Resource String Constants A string constant defined with the resourcestring directive is stored in the resources of the program, in a string table. resourcestring CompanyName = 'Delphi'; procedure TForm1.Button1Click(Sender: TObject); begin ShowMessage (CompanyName); end; The interesting aspect of this is that if you examine it with a resource explorer you'll see the new strings in the resources. This means that the strings are not part of the compiled code but stored in a separate area of the executable file. The advantage of resources is in an efficient memory handling performed by Windows and in the possibility of localizing a program (translating the strings to a different language) without having to modify its source code. Data Types In Pascal there are several predefined data types, which can be divided into three groups: ordinal types, real types, and strings. Ordinal Types Size Signed Range ''' '''Unsigned Range 8 bits ShortInt(-128 to 127) Byte (0 to 255) 16 bits SmallInt (-32768 to 32767) Word (0 to 65,535) 32 bits LongInt (-2,147,483,648 to 2,147,483,647) LongWord (0 to 4,294,967,295) 64 bits Int64 16/32 bits Integer Cardinal Ordinal Types Routines Routine Purpose • Dec Decrements the variable passed as parameter, by one or by the value of the optional second parameter. • Inc Increments the variable passed as parameter, by one or by the specified value. • Odd Returns True if the argument is an odd number. • Pred Returns the value before the argument in the order determined by the data type, the predecessor. • Succ Returns the value after the argument, the successor. • Ord Returns a number indicating the order of the argument within the set of values of the data type. • Low Returns the lowest value in the range of the ordinal type passed as its parameter. • High Returns the highest value in the range of the ordinal data type. Real Types Real types represent floating-point numbers in various formats. The smallest storage size is given by Single numbers, which are implemented with a 4-byte value. Then there are Double floating-point numbers, implemented with 8 bytes, and Extended numbers, implemented with 10 bytes. Date and Time TDateTime is not a predefined type the compiler understands, but it is defined in the system unit as: type TDateTime = type Double; System Routines for the TDateTime Type: Routine Description ¤ Now Returns the current date and time into a single TDateTime value. ¤ Date Returns only the current System date. ¤ Time Returns only the current System time. ¤ DateTimeToStr Converts a date and time value into a string, using default formatting; to have more control on the conversion use the FormatDateTime function instead. ¤ DateTimeToString Copies the date and time values into a string buffer, with default formatting. ¤ DateToStr Converts the date portion of a TDateTime value into a string. ¤ TimeToStr Converts the time portion of a TDateTime value into a string. ¤ FormatDateTime Formats a date and time using the specified format; you can specify which values you want to see and which format to use, providing a complex format string. ¤ StrToDateTime Converts a string with date and time information to a TDateTime value, raising an exception in case of an error in the format of the string. ¤ StrToDate Converts a string with a date value into the TDateTime format. ¤ StrToTime Converts a string with a time value into the TDateTime format. ¤ DayOfWeek Returns the number corresponding to the day of the week of the TDateTime value passed as parameter. ¤ DecodeDate Retrieves the year, month, and day values from a date value. ¤ DecodeTime Retrieves out of a time value. ¤ EncodeDate Turns year, month, and day values into a TDateTime value. ¤ EncodeTime Turns hour, minute, second, and millisecond values into a TDateTime value. Besides calling TimeToStr and DateToStr you can use the more powerful FormatDateTime function, as I've done in the last method above (see the Delphi Help file for details on the formatting parameters). Notice also that time and date values are transformed into strings depending on Windows international settings. Delphi reads these values from the system, and copies them to a number of global constants declared in the SysUtils unit. Some of them are: * DateSeparator: Char; * ShortDateFormat: string; * LongDateFormat: string; * TimeSeparator: Char; * TimeAMString: string; * TimePMString: string; * ShortTimeFormat: string; * LongTimeFormat: string; * ShortMonthNames: array 1..12 of string; * LongMonthNames: array 1..12 of string; * ShortDayNames: array 1..7 of string; * LongDayNames: array 1..7 of string; Typecasting and Type Conversions var N: Integer; C: Char; B: Boolean; begin N := Integer ('X'); C := Char (N); B := Boolean (0); System Routines for Type Conversion: Routine Description ¤ Chr Converts an ordinal number into an ANSI character. ¤ Ord Converts an ordinal-type value into the number indicating its order. ¤ Round Converts a real-type value into an Integer-type value, rounding its value. ¤ Trunc Converts a real-type value into an Integer-type value, truncating its value. ¤ Int Returns the Integer part of the floating-point value argument. ¤ IntToStr Converts a number into a string. ¤ IntToHex Converts a number into a string with its hexadecimal representation. ¤ StrToInt Converts a string into a number, raising an exception if the string does not represent a valid integer. ¤ StrToIntDef Converts a string into a number, using a default value if the string is not correct. ¤ Val Converts a string into a number (traditional Turbo Pascal routine, available for compatibility). ¤ Str Converts a number into a string, using formatting parameters (traditional Turbo Pascal routine, available for compatibility). ¤ StrPas Converts a null-terminated string into a Pascal-style string. This conversion is automatically done for AnsiStrings in 32-bit Delphi. ¤ StrPCopy Copies a Pascal-style string into a null-terminated string. This conversion is done with a simple PChar cast in 32-bit Delphi. ¤ StrPLCopy Copies a portion of a Pascal-style string into a null-terminated string. ¤ FloatToDecimal Converts a floating-point value to record including its decimal representation (exponent, digits, sign). ¤ FloatToStr Converts the floating-point value to its string representation using default formatting. ¤ FloatToStrF Converts the floating-point value to its string representation using the specified formatting. ¤ FloatToText Copies the floating-point value to a string buffer, using the specified formatting. ¤ FloatToTextFmt As the previous routine, copies the floating-point value to a string buffer, using the specified formatting. ¤ StrToFloat Converts the given Pascal string to a floating-point value. ¤ TextToFloat Converts the given null-terminated string to a floating-point value. Subrange Types A subrange type defines a range of values within the range of another type (hence the name subrange). You can define a subrange of the Integer type, from 1 to 10 or from 100 to 1000, or you can define a subrange of the Char type, as in: type Ten = 1..10; OverHundred = 100..1000; Uppercase = 'A'..'Z'; In the definition of a subrange, you don’t need to specify the name of the base type. You just need to supply two constants of that type. The original type must be an ordinal type, and the resulting type will be another ordinal type. When you have defined a subrange, you can legally assign it a value within that range. This code is valid: var UppLetter: UpperCase; begin UppLetter := 'F'; But this one is not: var UppLetter: UpperCase; begin UppLetter := 'e'; // compile-time error Writing the code above results in a compile-time error, "Constant expression violates subrange bounds." If you write the following code instead: var UppLetter: Uppercase; Letter: Char; begin Letter :='e'; UppLetter := Letter; Delphi will compile it. At run-time, if you have enabled the Range Checking compiler option (in the Compiler page of the Project Options dialog box), you’ll get a Range check error message. Enumerated Types Enumerated types constitute another user-defined ordinal type. Instead of indicating a range of an existing type, in an enumeration you list all of the possible values for the type. In other words, an enumeration is a list of values. Here are some examples: type Colors = (Red, Yellow, Green, Cyan, Blue, Violet); Suit = (Club, Diamond, Heart, Spade); Each value in the list has an associated ordinality, starting with zero. When you apply the Ord function to a value of an enumerated type, you get this zero-based value. For example, Ord (Diamond) returns 1. The Delphi VCL (Visual Component Library) uses enumerated types in many places. For example, the style of the border of a form is defined as follows: type TFormBorderStyle = (bsNone, bsSingle, bsSizeable, bsDialog, bsSizeToolWin, bsToolWindow); When the value of a property is an enumeration, you usually can choose from the list of values displayed in the Object Inspector, as shown in Figure. Figure: An enumerated type property in the Object Inspector The Delphi Help file generally lists the possible values of an enumeration. As an alternative you can use the OrdType program, available on http://www.marcocantu.com, to see the list of the values of each Delphi enumeration, set, subrange, and any other ordinal type. Set Types Set types indicate a group of values, where the list of available values is indicated by the ordinal type the set is based onto. These ordinal types are usually limited, and quite often represented by an enumeration or a subrange. If we take the subrange 1..3, the possible values of the set based on it include only 1, only 2, only 3, both 1 and 2, both 1 and 3, both 2 and 3, all the three values, or none of them. A variable usually holds one of the possible values of the range of its type. A set-type variable, instead, can contain none, one, two, three, or more values of the range. It can even include all of the values. Here is an example of a set: type Letters = set of Uppercase; Now I can define a variable of this type and assign to it some values of the original type. To indicate some values in a set, you write a comma-separated list, enclosed within square brackets. The following code shows the assignment to a variable of several values, a single value, and an empty value: var Letters1, Letters2, Letters3: Letters; begin Letters1 := 'B', 'C'; Letters2 := 'K'; Letters3 := []; In Delphi, a set is generally used to indicate nonexclusive flags. For example, the following two lines of code (which are part of the Delphi library) declare an enumeration of possible icons for the border of a window and the corresponding set type. type TBorderIcon = (biSystemMenu, biMinimize, biMaximize, biHelp); TBorderIcons = set of TBorderIcon; A set-type property in the Object Inspector: Another property based on a set type is the style of a font. Possible values indicate a bold, italic, underline, and strikethrough font. Of course the same font can be both italic and bold, have no attributes, or have them all. For this reason it is declared as a set. You can assign values to this set in the code of a program as follows: Font.Style := []; // no style Font.Style := fsBold; // bold style only Font.Style := fsItalic; // two styles Array Types Array types define lists of a fixed number of elements of a specific type. You generally use an index within square brackets to access to one of the elements of the array. The square brackets are used also to specify the possible values of the index when the array is defined. For example, you can define a group of 24 integers with this code: type DayTemperatures = array 1..24 of Integer; In the array definition, you need to pass a subrange type within square brackets, or define a new specific subrange type using two constants of an ordinal type. This subrange specifies the valid indexes of the array. Since you specify both the upper and the lower index of the array, the indexes don’t need to be zero-based, as is necessary in C, C++, Java, and other programming languages. Simple and Compound Statements A Pascal statement is simple when it doesn't contain any other statements. Examples of simple statements are assignment statements and procedure calls. Simple statements are separated by a semicolon: X := Y + Z; // assignment Randomize; // procedure call Usually, statements are part of a compound statement, marked by begin and end brackets. A compound statement can appear in place of a generic Pascal statement. Here is an example: begin A := B; C := A * 2; end; The semicolon after the last statement before the end isn't required, as in the following: begin A := B; C := A * 2 end; Both versions are correct. The first version has a useless (but harmless) semicolon. This semicolon is, in fact, a null statement; that is, a statement with no code. Notice that, at times, null statements can be used inside loops or in other particular cases. Conditional Statements A conditional statement is used to execute either one of the statements it contains or none of them, depending on some test. There are two basic flavors of conditional statements: if statements and case statements. If Statements procedure TForm1.Button1Click(Sender: TObject); begin { simple if statement } if CheckBox1.Checked then ShowMessage ('CheckBox1 is checked') end; procedure TForm1.Button2Click(Sender: TObject); begin // if-then-else statement if CheckBox2.Checked then ShowMessage ('CheckBox2 is checked') else ShowMessage ('CheckBox2 is NOT checked'); end; Notice that you cannot have a semicolon after the first statement and before the else keyword, or the compiler will issue a syntax error. The if-then-else statement, in fact, is a single statement, so you cannot place a semicolon in the middle of it. procedure TForm1.Button4Click(Sender: TObject); begin // compound if statement if CheckBox1.Checked then if CheckBox2.Checked then ShowMessage ('CheckBox1 and 2 are checked') else ShowMessage ('Only CheckBox1 is checked') else ShowMessage ('Checkbox1 is not checked, who cares for Checkbox2?') end; Case Statements If your if statements become very complex, at times you can replace them with case statements. A case statement consists in an expression used to select a value, a list of possible values, or a range of values. These values are constants, and they must be unique and of an ordinal type. Eventually, there can be an else statement that is executed if none of the labels correspond to the value of the selector. Here are two simple examples: case Number of 1: Text := 'One'; 2: Text := 'Two'; 3: Text := 'Three'; end; case MyChar of '+' : Text := 'Plus sign'; '-' : Text := 'Minus sign'; '*', '/': Text := 'Multiplication or division'; '0'..'9': Text := 'Number'; 'a'..'z': Text := 'Lowercase character'; 'A'..'Z': Text := 'Uppercase character'; else Text := 'Unknown character'; end; Loops in Pascal The Pascal language has the typical repetitive statements of most programming languages, including for, while, and repeat statements. Most of what these loops do will be familiar if you've used other programming languages, so I'll cover them only briefly. The For Loop The for loop in Pascal is strictly based on a counter, which can be either increased or decreased each time the loop is executed. Here is a simple example of a for loop used to add the first ten numbers. var K, I: Integer; begin K := 0; for I := 1 to 10 do K := K + I; This same for statement could have been written using a reverse counter: var K, I: Integer; begin K := 0; for I := 10 downto 1 do K := K + I; The for loop in Pascal is less flexible than in other languages (it is not possible to specify an increment different than one), but it is simple and easy to understand. If you want to test for a more complex condition, or to provide a customized counter, you need to use a while or repeat statement, instead of a for loop. Note: The counter of a for loop doesn't need to be a number. It can be a value of any ordinal type, such as a character or an enumerated type. While and Repeat Statements The difference between the while-do loop and the repeat-until loop is that the code of the repeat statement is always executed at least once. You can easily understand why by looking at a simple example: while (I <= 100) and (J <= 100) do begin // use I and J to compute something... I := I + 1; J := J + 1; end; repeat // use I and J to compute something... I := I + 1; J := J + 1; until (I > 100) or (J > 100); If the initial value of I'' or ''J is greater than 100, the statements inside the repeat-until loop are executed once anyway. The other key difference between these two loops is that the repeat-until loop has a reversed condition. The loop is executed as long as the condition is not met. When the condition is met, the loop terminates. This is the opposite from a while-do loop, which is executed while the condition is true. For this reason I had to reverse the condition in the code above to obtain a similar statement. The With Statement Form1.Canvas.Pen.Width := 2; Form1.Canvas.Pen.Color := clRed; But it is certainly easier to write this code: with Form1.Canvas.Pen do begin Width := 2; Color := clRed; end; When you are writing complex code, the with statement can be effective and spares you the declaration of some temporary variables, but it has a drawback. It can make the code less readable, particularly when you are working with different objects that have similar or corresponding properties. A further drawback is that using the with statement can allow subtle logical errors in the code that the compiler will not detect. For example: with Button1 do begin Width := 200; Caption := 'New Caption'; Color := clRed; end; This code changes the Caption and the Width of the button, but it affects the Color property of the form, not that of the button! The reason is that the TButton components don't have the Color property, and since the code is executed for a form object (we are writing a method of the form) this object is accessed by default. If we had instead written: Button1.Width := 200; Button1.Caption := 'New Caption'; Button1.Color := clRed; // error! Pascal Procedures and Functions In Pascal, a routine can assume two forms: a procedure and a function. In theory, a procedure is an operation you ask the computer to perform, a function is a computation returning a value. This difference is emphasized by the fact that a function has a result, a return value, while a procedure doesn't. Both types of routines can have multiple parameters, of given data types. procedure Hello; begin ShowMessage ('Hello world!'); end; function Double (Value: Integer) : Integer; begin Double := Value * 2; end; // or, as an alternative function Double2 (Value: Integer) : Integer; begin Result := Value * 2; end; Reference Parameters Pascal routines allow parameter passing by value and by reference. Passing parameters by value is the default: the value is copied on the stack and the routine uses and manipulates the copy, not the original value. Passing a parameter by reference means that its value is not copied onto the stack in the formal parameter of the routine (avoiding a copy often means that the program executes faster). Instead, the program refers to the original value, also in the code of the routine. This allows the procedure or function to change the value of the parameter. Parameter passing by reference is expressed by the var keyword. Here is an example of passing a parameter by reference using the var keyword: procedure DoubleTheValue (var Value: Integer); begin Value := Value * 2; end; In this case, the parameter is used both to pass a value to the procedure and to return a new value to the calling code. When you write: var X: Integer; begin X := 10; DoubleTheValue (X); The value of the X variable becomes 20, because the function uses a reference to the original memory location of X, affecting its initial value. Passing parameters by reference makes sense for ordinal types, for old-fashioned strings, and for large records. Delphi objects, in fact, are invariably passed by value, because they are references themselves. For this reason passing an object by reference makes little sense (apart from very special cases), because it corresponds to passing a "reference to a reference." Constant Parameters The keyword is used to start a section of constant definitions. The section is terminated by the next keyword in a program. Within the section, one or more constants may be defined. These can be a mixture of normal or typed constants: These give a name Name1 to a fixed expression,Expression1. The expression must resolve into one of the following types: These are very odd. They are constant only in the sense that their value persists all parts of a program. Yet it can be changed (as long as the compiler directive {WriteableConst} is set On). They are used, for example, when a routine needs to hold values that are preserved across calls. It is better to use Object Oriented principles to allow data to be preserved across accesses. When passing data to a routine (function or procedure), you can prefix the parameter definition with if the value is never updated. This marginally improves performance, clarifies routine operation, and prevents accidental updates of the value. function DoubleTheValue (const Value: Integer): Integer; begin Value := Value * 2; // compiler error Result := Value; end; Dynamic Arrays procedure TForm1.Button1Click(Sender: TObject); var Array1: array of Integer; begin Array1 1 := 100; // error SetLength (Array1, 100); Array1 99 := 100; // OK ... end; Variants Have No Type In general, you can use variants to store any data type and perform numerous operations and type conversions. Notice that this goes against the general approach of the Pascal language and against good programming practices. A variant is type-checked and computed at run time. The compiler won't warn you of possible errors in the code, which can be caught only with extensive testing. var V: Variant; you can assign to it values of several different types: V := 10; V := 'Hello, World'; V := 45.55; Once you have the variant value, you can copy it to any compatible-or incompatible-data type. If you assign a value to an incompatible data type, Delphi performs a conversion, if it can. Otherwise it issues a run-time error. procedure TForm1.Button1Click(Sender: TObject); var V: Variant; begin V := 10; Edit1.Text := V; V := 'Hello, World'; Edit2.Text := V; V := 45.55; Edit3.Text := V; end; Category:References